package com.flute.tools.data.berkeleydb;

import java.io.File;
import java.util.Iterator;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

public class BerkelyDB {

	private static final Logger logger = Logger.getLogger(BerkelyDB.class);

	private File envDir;
	private Environment exampleEnv;
	private Database exampleDb;
	private Cursor cursor;
	private EntryBinding<Object> dataBinding;
	private EntryBinding<Object> keyBinding;

	/**
	 * Initial Berkeley database
	 * 
	 * @param bdbDir
	 * @param dbName
	 * @param binding
	 */
	public BerkelyDB(String bdbDir, String dbName, EntryBinding<Object> binding) {

		this.keyBinding = binding;

		this.envDir = new File(bdbDir);
		EnvironmentConfig envConfig = new EnvironmentConfig();
		envConfig.setAllowCreate(true);

		try {
			exampleEnv = new Environment(envDir, envConfig);
			initialSerialBinding();

			DatabaseConfig dbConfig = DatabaseConfig.DEFAULT;

			dbConfig.setAllowCreate(true);
			dbConfig.setDeferredWrite(true);

			exampleDb = exampleEnv.openDatabase(null, dbName, dbConfig);
		} catch (Exception e) {
			logger.error("Berkely Database Exception.", e);
		}
	}

	public BerkelyDB(String bdbDir, String dbName,
			EntryBinding<Object> keyBinding, EntryBinding<Object> dataBinding) {
		this.keyBinding = keyBinding;
		this.dataBinding = dataBinding;
		this.envDir = new File(bdbDir);
		EnvironmentConfig envConfig = new EnvironmentConfig();
		envConfig.setAllowCreate(true);

		try {
			exampleEnv = new Environment(envDir, envConfig);
			DatabaseConfig dbConfig = DatabaseConfig.DEFAULT;

			dbConfig.setAllowCreate(true);
			dbConfig.setDeferredWrite(true);
			exampleDb = exampleEnv.openDatabase(null, dbName, dbConfig);
		} catch (Exception e) {
			logger.error("Berkely Database Exception.", e);
		}
	}

	private void initialSerialBinding() {
		DatabaseConfig config = new DatabaseConfig();
		config.setAllowCreate(true);
		Database classCatalogDB = exampleEnv.openDatabase(null, "classCatalog",
				config);
		StoredClassCatalog classCatalog = new StoredClassCatalog(classCatalogDB);
		this.dataBinding = new SerialBinding<Object>(classCatalog, Object.class);
	}

	public Cursor getCursor() {
		try {
			cursor = exampleDb.openCursor(null, null);
			return cursor;
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public Iterator<Object> valueIterator() {
		return new Iterator<Object>() {
			boolean last = false;
			{
				try {
					if (cursor != null) {
						cursor.close();
					}
					DatabaseEntry keyEntry = new DatabaseEntry();
					DatabaseEntry dataEntry = new DatabaseEntry();
					cursor = exampleDb.openCursor(null, null);

					last = !(cursor.getFirst(keyEntry, dataEntry, null) == OperationStatus.SUCCESS);
				} catch (DatabaseException e1) {
					e1.printStackTrace();
				}
			}

			public boolean hasNext() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					if (cursor.getNext(keyEntry, dataEntry, null) == OperationStatus.SUCCESS) {
						cursor.getPrev(keyEntry, dataEntry, null);
						return true;
					} else if (last == false) {
						return true;
					}
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return false;
			}

			public Object next() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					cursor.getCurrent(keyEntry, dataEntry, LockMode.DEFAULT);
					Object object = dataBinding.entryToObject(dataEntry);
					last = !(cursor.getNext(keyEntry, dataEntry,
							LockMode.DEFAULT) == OperationStatus.SUCCESS);

					return object;
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return null;
			}

			public void remove() {
			}
		};
	}

	public Iterator<Object> keyIterator() {
		return new Iterator<Object>() {
			boolean last = false;
			{
				try {
					if (cursor != null) {
						cursor.close();
					}
					DatabaseEntry keyEntry = new DatabaseEntry();
					DatabaseEntry dataEntry = new DatabaseEntry();
					cursor = exampleDb.openCursor(null, null);
					last = !(cursor.getFirst(keyEntry, dataEntry, null) == OperationStatus.SUCCESS);
				} catch (DatabaseException e1) {
					e1.printStackTrace();
				}
			}

			public boolean hasNext() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					if (cursor.getNext(keyEntry, dataEntry, null) == OperationStatus.SUCCESS) {
						cursor.getPrev(keyEntry, dataEntry, null);

						return true;
					} else if (last == false) {
						return true;
					}
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return false;
			}

			public Object next() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					cursor.getCurrent(keyEntry, dataEntry, LockMode.DEFAULT);
					Object object = keyBinding.entryToObject(keyEntry);
					last = !(cursor.getNext(keyEntry, dataEntry,
							LockMode.DEFAULT) == OperationStatus.SUCCESS);
					return object;
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return null;
			}

			public void remove() {
			}
		};
	}

	public Iterator<Entry<Object, Object>> iterator() {

		return new Iterator<Entry<Object, Object>>() {
			boolean last = false;
			{
				try {
					if (cursor != null) {
						cursor.close();
					}
					DatabaseEntry keyEntry = new DatabaseEntry();
					DatabaseEntry dataEntry = new DatabaseEntry();
					cursor = exampleDb.openCursor(null, null);
					last = !(cursor.getFirst(keyEntry, dataEntry, null) == OperationStatus.SUCCESS);
				} catch (DatabaseException e1) {
					e1.printStackTrace();
				}
			}

			public boolean hasNext() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					if (cursor.getNext(keyEntry, dataEntry, null) == OperationStatus.SUCCESS) {
						cursor.getPrev(keyEntry, dataEntry, null);

						return true;
					} else if (last == false) {
						return true;
					}
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return false;
			}

			public Entry<Object, Object> next() {
				DatabaseEntry keyEntry = new DatabaseEntry();
				DatabaseEntry dataEntry = new DatabaseEntry();
				try {
					cursor.getCurrent(keyEntry, dataEntry, LockMode.DEFAULT);
					Object key = keyBinding.entryToObject(keyEntry);
					Object value = dataBinding.entryToObject(dataEntry);
					last = !(cursor.getNext(keyEntry, dataEntry,
							LockMode.DEFAULT) == OperationStatus.SUCCESS);
					return new BdbEntry(key, value);
				} catch (DatabaseException e) {
					e.printStackTrace();
				}

				return null;
			}

			public void remove() {
			}
		};
	}

	public Editor editor() {
		return new Editor();
	}

	public void close() {
		try {
			if (cursor != null) {
				cursor.close();
			}
			exampleDb.close();
			exampleEnv.close();
		} catch (DatabaseException e) {
			logger.error("Berkely Database Exception.", e);
		}
	}

	public class Editor {
		int count = 0;

		public Editor() {
			try {
				if (cursor != null) {
					cursor.close();
				}
				cursor = exampleDb.openCursor(null, null);
			} catch (DatabaseException e1) {
				logger.error("Berkely Database Exception.", e1);
			}
		}

		public void put(Object data) {
			count++;
			if (count > 999) {
				try {
					exampleDb.sync();
				} catch (Exception e) {
					e.printStackTrace();
				}
				count = 0;
			}
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry dataEntry = new DatabaseEntry();

			try {
				keyBinding.objectToEntry(data, keyEntry);
				if (cursor.getSearchKey(keyEntry, dataEntry, LockMode.DEFAULT) != OperationStatus.NOTFOUND) {
					cursor.delete();
					System.out.println("delete: " + data.toString());
				}
				dataBinding.objectToEntry(data, dataEntry);
				exampleDb.put(null, keyEntry, dataEntry);
			} catch (DatabaseException e) {
				e.printStackTrace();
			}
		}

		public Object getEntry(Object key) {
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry dataEntry = new DatabaseEntry();
			try {
				keyBinding.objectToEntry(key, keyEntry);
				if (cursor.getSearchKey(keyEntry, dataEntry, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					return dataBinding.entryToObject(dataEntry);
				}
			} catch (DatabaseException e) {
				e.printStackTrace();
			}

			return null;
		}

		public boolean deleteEntry(Object key) {
			DatabaseEntry keyEntry = new DatabaseEntry();
			DatabaseEntry dataEntry = new DatabaseEntry();
			try {
				StringBinding.stringToEntry(key.toString(), keyEntry);
				if (cursor.getSearchKey(keyEntry, dataEntry, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					cursor.delete();
				}
				return true;
			} catch (DatabaseException e) {
				e.printStackTrace();
				return false;
			}
		}

		public void close() {
			try {
				if (cursor != null) {
					cursor.close();
				}
			} catch (DatabaseException e) {
				logger.error("Berkely Database Exception.", e);
			}
		}
	}

	public class BdbEntry implements Entry<Object, Object> {
		private Object key;
		private Object value;

		public BdbEntry() {
		}

		public BdbEntry(Object key, Object value) {
			this.key = key;
			this.value = value;
		}

		public Object getKey() {
			return key;
		}

		public void setKey(Object key) {
			this.key = key;
		}

		public Object getValue() {
			return value;
		}

		public Object setValue(Object value) {
			this.value = value;
			return null;
		}
	}
}
